using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Diagnostics.Contracts;

namespace Konst3d.Efficiency.IO
{
    public interface IListFileLayout<T>
    {
        int ByteSize
        {
            get;
        }

        void Write(T value, BinaryWriter writer, int offset = -1, SeekOrigin origin = SeekOrigin.Begin);
        T Read(BinaryReader reader, int offset = -1, SeekOrigin origin = SeekOrigin.Begin);

        bool Equals(T arg1, T arg2);
        int Compare(T arg1, T arg2);
    }

    public /*internal*/ class ListFile<T>: BinaryFile, IList<T>
    {
        #region Types

        internal class Entry
        {
            #region Constants

            private const int OFFSET_ORDINAL = 0;
            private const int OFFSET_VALUE = 4;

            #endregion // Constant

            #region Properties

            public int Ordinal
            {
                get
                {
                    return _ordinal;
                }
                set
                {
                    _writer.Write(value, _offset + OFFSET_ORDINAL);
                    _ordinal = value;
                }
            }

            public T Value
            {
                get
                {
                    return _value;
                }
                set
                {
                    _format.Write(value, _writer, _offset + OFFSET_VALUE, SeekOrigin.Begin);
                    _value = value;
                }
            }

            #endregion // Properties

            #region Constructor

            public Entry(int offset, BinaryWriter writer, IListFileLayout<T> format, int ordinal, T value)
            {
                _offset  = offset;
                _writer  = writer;
                _format  = format;

                Ordinal = ordinal;
                Value = value;
            }

            #endregion // Constructor

            public static Entry Read(int offset, BinaryReader reader, BinaryWriter writer, IListFileLayout<T> format)
            {
                int ordinal = reader.ReadInt32(offset + OFFSET_ORDINAL);
                T value = format.Read(reader, offset + OFFSET_VALUE);

                return new Entry(offset, writer, format, ordinal, value);
            }

            #region Fields

            private BinaryWriter _writer;
            private int _offset;
            private int _ordinal;
            private T _value;
            private IListFileLayout<T> _format;

            #endregion // Fields
        }

        internal class Enumerator : IEnumerator<T>
        {
            public T Current
            {
                get
                {
                    return _enumerator.Current.Value;
                }
            }

            object System.Collections.IEnumerator.Current
            {
                get
                {
                    return _enumerator.Current.Value;
                }
            }

            public Enumerator(List<Entry>.Enumerator enumerator)
            {
                _enumerator = enumerator;
            }

            public bool MoveNext()
            {
                return _enumerator.MoveNext();
            }

            public void Reset()
            {
                (_enumerator as IEnumerator<Entry>).Reset();
            }

            public void Dispose()
            {
                _enumerator.Dispose();
            }

            private List<Entry>.Enumerator _enumerator;
        }

        #endregion // Types

        #region Constants

        private const uint SIGNATURE = 0x36363636;
        private const uint VERSION = 1;
        private const int  OFFSET_CAPACITY = 8;
        private const int  OFFSET_DATA = 12;

        #endregion // Constants

        #region Properties

        public int Capacity
        {
            get
            {
                return _capacity;
            }
            private set
            {
                _writer.Write(value, OFFSET_CAPACITY);
                _capacity = value;
            }
        }

        #endregion // Properties

        #region Create/Open Methods

        public static ListFile<T> Create(IListFileLayout<T> layout, bool allowDuplicates, string path, bool overwrite)
        {
            Contract.Requires(!String.IsNullOrEmpty(path));

            if (!overwrite && File.Exists(path))
            {
                throw new IOException(String.Format("File is already exists {0}", path));
            }

            FileStream stream = null;
            BinaryReader reader = null;
            BinaryWriter writer = null;

            try
            {
                stream = File.Open(path, FileMode.Create, FileAccess.ReadWrite, FileShare.Read);
                reader = new BinaryReader(stream, Encoding.UTF8);
                writer = new BinaryWriter(stream, Encoding.UTF8);

                /* TOFIX: remove
                stream.Write(BitConverter.GetBytes(SIGNATURE), 0, 4);
                stream.Write(BitConverter.GetBytes(VERSION), 0, 4);
                stream.Write(BitConverter.GetBytes((int)0), 0, 4);

                stream.Flush();
                stream.Seek(0, SeekOrigin.Begin);
                */

                writer.Write(SIGNATURE);
                writer.Write(VERSION);
                writer.Write((int)0);

                writer.Flush();
                writer.Seek(0, SeekOrigin.Begin);

                return new ListFile<T>(stream, reader, writer, layout, allowDuplicates);
            }
            catch
            {
                if (stream != null)
                {
                    stream.Dispose();
                }

                if (reader != null)
                {
                    reader.Dispose();
                }

                if (writer != null)
                {
                    writer.Dispose();
                }

                throw;
            }
        }

        public static ListFile<T> Open(IListFileLayout<T> layout, bool allowDuplicates, string path)
        {
            Contract.Requires(!String.IsNullOrEmpty(path));

            if (!File.Exists(path))
            {
                throw new IOException(String.Format("Failed to locate labels file {0}.", path));
            }

            FileStream stream = null;
            BinaryReader reader = null;
            BinaryWriter writer = null;

            try
            {
                stream = File.Open(path, FileMode.Open, FileAccess.ReadWrite, FileShare.Read);
                reader = new BinaryReader(stream, Encoding.UTF8);
                writer = new BinaryWriter(stream, Encoding.UTF8);

                return new ListFile<T>(stream, reader, writer, layout, allowDuplicates);
            }
            catch
            {
                if (stream != null)
                {
                    stream.Dispose();
                }

                if (reader != null)
                {
                    reader.Dispose();
                }

                if (writer != null)
                {
                    writer.Dispose();
                }

                throw;
            }
        }

        #endregion // Create/Open Methods

        #region Constructor

        private ListFile(FileStream stream, BinaryReader reader, BinaryWriter writer, IListFileLayout<T> layout, bool allowDuplicates) :
            base(stream, reader, writer, SIGNATURE, VERSION)
        {
            Contract.Requires(layout != null);

            _allowDuplicates = allowDuplicates;
            _layout = layout;

            _capacity = _reader.ReadInt32();

            for (int i = 0; i < _capacity; ++i)
            {
                var entry = Entry.Read(GetEntryOffset(i), _reader, _writer, _layout);

                if (entry.Ordinal >= 0)
                {
                    _entries.Add(entry);
                }
                else
                {
                    _empty.Add(entry);
                }
            }

            _entries.Sort((x, y) =>
            {
                if (x.Ordinal == y.Ordinal)
                {
                    return 0;
                }

                return x.Ordinal > y.Ordinal ? 1 : -1;
            });
        }

        #endregion // Constructor

        #region IList<T> Members

        public int IndexOf(T item)
        {
            int index = -1;

            foreach (var entry in _entries)
            {
                if (_layout.Equals(entry.Value, item))
                {
                    index = entry.Ordinal;
                    break;
                }
            }

            return index;
        }

        public void Insert(int index, T item)
        {
            Contract.Requires(index < Count);

            if (!_allowDuplicates)
            {
                Contract.Assert(!Contains(item));
            }

            Entry entry = null;

            if (_empty.Count > 0)
            {
                entry = _empty[0];

                entry.Ordinal = index;
                entry.Value = item;

                _empty.RemoveAt(0);
            }
            else
            {
                entry = new Entry((int)_stream.Length, _writer, _layout, index, item);
                Capacity += 1;
            }

            _entries.Insert(index, entry);

            for (int i = index + 1; i < _entries.Count; ++i)
            {
                _entries[i].Ordinal += 1;
            }
        }

        public void RemoveAt(int index)
        {
            Contract.Requires(index < Count);

            var entry = _entries[index];

            entry.Ordinal = -1;

            _entries.RemoveAt(index);
            _empty.Add(entry);

            for (int i = index; i < _entries.Count; ++i)
            {
                _entries[i].Ordinal -= 1;
            }
        }

        public T this[int index]
        {
            get
            {
                Contract.Requires(index < Count);

                return _entries[index].Value;
            }
            set
            {
                Contract.Requires(index < Count);

                if (!_allowDuplicates)
                {
                    Contract.Assert(!Contains(value));
                }

                _entries[index].Value = value;
            }
        }

        public void Add(T item)
        {
            if(!_allowDuplicates)
            {
                Contract.Assert(!Contains(item));
            }

            Entry entry = null;

            if (_empty.Count > 0)
            {
                entry = _empty[0];

                entry.Ordinal = _entries.Count;
                entry.Value = item;

                _empty.RemoveAt(0);
            }
            else
            {
                entry = new Entry((int)_stream.Length, _writer, _layout, _entries.Count, item);
                Capacity += 1;
            }

            _entries.Add(entry);
        }

        public void Clear()
        {
            for (int i = 0; i < _entries.Count; ++i)
            {
                var entry = _entries[i];

                entry.Ordinal = -1;
                _empty.Add(entry);
            }

            _entries.Clear();
        }

        public bool Contains(T item)
        {
            return _entries.Find(entry => { return _layout.Equals(entry.Value, item); }) != null;
        }

        public void CopyTo(T[] array, int arrayIndex)
        {
            for (int i = 0; i + arrayIndex < array.Length && i < _entries.Count; ++i)
            {
                array[i + arrayIndex] = _entries[i].Value;
            }
        }

        public int Count
        {
            get
            {
                return _entries.Count;
            }
        }

        public bool IsReadOnly
        {
            get
            {
                return false;
            }
        }

        public bool Remove(T item)
        {
            bool removed = false;
            int index = IndexOf(item);

            if (index >= 0)
            {
                RemoveAt(index);
                removed = true;
            }

            return removed;
        }

        public IEnumerator<T> GetEnumerator()
        {
            return new Enumerator(_entries.GetEnumerator());
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return new Enumerator(_entries.GetEnumerator());
        }

        #endregion // IList<T> Members

        #region Private Methods

        private int GetEntryOffset(int index)
        {
            return OFFSET_DATA + index * (4 + _layout.ByteSize); // ordinal + value
        }

        #endregion // Private Methods

        #region Fields

        private bool _allowDuplicates = false;
        private int _capacity;
        private IListFileLayout<T> _layout;
        private List<Entry> _entries = new List<Entry>();
        private List<Entry> _empty = new List<Entry>();

        #endregion // Fields
    }
}
